package com.kecheng.servermonitor.config.task;

import com.kecheng.servermonitor.config.cache.BackupConfig;
import lombok.val;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.io.*;
import java.nio.file.*;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.concurrent.TimeUnit;

@Service
public class DatabaseBackupTask {

    @Resource
    private BackupConfig config;

    private static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("yyyyMMdd_HHmmss");

    /**
     * 每日备份 - 生产环境使用
     */
    //@Scheduled(cron = "0 0 2 * * ?")//每天凌晨两点开始执行
    @Scheduled(fixedDelay = 2000000)
    public void performDailyBackup() {
        val enabled = config.getConfig().isEnabled();
        if (!enabled) {
            System.out.println("不起用数据库定时备份任务！！！");
            return;
        }
        String timestamp = DATE_FORMAT.format(new Date());
        System.out.println("[" + timestamp + "] 开始执行数据库备份...");
        long startTime = System.currentTimeMillis();
        try {
            createBackupDirectory();
            String backupFileName = "backup_" + getDatabaseName() + "_" + timestamp + ".sql";
            Path backupPath = Paths.get(config.getConfig().getBackupDirectory(), backupFileName);
            // 获取数据库中实际表数量
            int actualTableCount = getActualTableCount();
            if (actualTableCount > 0) {
                System.out.println("📊 数据库中实际表数量: " + actualTableCount);
            } else {
                System.out.println("⚠️ 无法获取数据库表数量，将只验证备份文件内部一致性");
            }

            // 执行备份
            boolean success = executeMysqlDump(backupPath);

            if (success) {
                // 重新组织备份文件：表结构在前，数据在后
                boolean reorganized = reorganizeBackupFile(backupPath);

                if (reorganized) {
                    // 验证备份文件并对比表数量
                    boolean validated = validateBackupFile(backupPath, actualTableCount);

                    if (validated) {
                        long endTime = System.currentTimeMillis();
                        long duration = (endTime - startTime) / 1000;
                        long fileSize = Files.size(backupPath) / 1024 / 1024;

                        System.out.println("==========================================");
                        System.out.println("✅ 数据库备份成功!");
                        System.out.println("📁 文件: " + backupFileName);
                        System.out.println("📊 大小: " + fileSize + " MB");
                        System.out.println("⏱️  耗时: " + duration + " 秒");
                        if (actualTableCount > 0) {
                            System.out.println("📋 表数量: " + actualTableCount + " 张表");
                        }
                        System.out.println("==========================================");

                        cleanupOldBackups();
                    } else {
                        System.err.println("❌ 备份文件验证失败");
                        Files.deleteIfExists(backupPath);
                    }
                } else {
                    System.err.println("❌ 备份文件重组失败");
                    Files.deleteIfExists(backupPath);
                }
            } else {
                System.err.println("❌ 数据库备份失败");
                Files.deleteIfExists(backupPath);
            }

        } catch (Exception e) {
            System.err.println("💥 备份过程中发生错误: " + e.getMessage());
            e.printStackTrace();
        }
    }

    /**
     * 获取数据库中实际表数量 - 使用mysqldump方式
     */
    private int getActualTableCount() {
        Path configFile = null;
        Path tempBackup = null;
        try {
            // 创建临时配置文件
            configFile = Files.createTempFile("mysql_count_", ".cnf");
            String configContent = String.format(
                    "[client]\nuser = %s\npassword = %s\nhost = localhost",
                    config.getDatabase().getUsername(),
                    config.getDatabase().getPassword()
            );
            Files.write(configFile, configContent.getBytes());

            // 创建临时备份文件
            tempBackup = Files.createTempFile("temp_backup_", ".sql");

            // 使用mysqldump只备份表结构
            ProcessBuilder processBuilder = new ProcessBuilder(
                    config.getConfig().getMysqlDumpPath(),
                    "--defaults-file=" + configFile.toString(),
                    "--no-data", // 只备份表结构，不备份数据
                    "--skip-comments",
                    "--skip-tz-utc",
                    getDatabaseName()
            );

            processBuilder.redirectOutput(tempBackup.toFile());
            processBuilder.redirectErrorStream(true);

            Process process = processBuilder.start();

            // 读取错误输出
            StringBuilder errorOutput = new StringBuilder();
            try (BufferedReader errorReader = new BufferedReader(
                    new InputStreamReader(process.getErrorStream()))) {
                String line;
                while ((line = errorReader.readLine()) != null) {
                    errorOutput.append(line).append("\n");
                }
            }

            int exitCode = process.waitFor();

            if (exitCode == 0) {
                // 统计CREATE TABLE语句数量
                int tableCount = 0;
                Set<String> tableNames = new HashSet<>();
                try (BufferedReader reader = Files.newBufferedReader(tempBackup)) {
                    String line;
                    while ((line = reader.readLine()) != null) {
                        if (line.trim().startsWith("CREATE TABLE")) {
                            tableCount++;
                            // 提取表名
                            String[] parts = line.trim().split("`");
                            if (parts.length >= 2) {
                                tableNames.add(parts[1]);
                            }
                        }
                    }
                }
                System.out.println("📊 通过表结构分析得到表数量: " + tableCount + " (唯一表名: " + tableNames.size() + ")");
                return tableCount;
            } else {
                System.err.println("获取表数量失败，mysqldump退出码: " + exitCode);
                System.err.println("错误信息: " + errorOutput);
                return -1;
            }

        } catch (Exception e) {
            System.err.println("获取表数量失败: " + e.getMessage());
            return -1;
        } finally {
            // 清理临时文件
            if (configFile != null) {
                try { Files.deleteIfExists(configFile); } catch (IOException e) {}
            }
            if (tempBackup != null) {
                try { Files.deleteIfExists(tempBackup); } catch (IOException e) {}
            }
        }
    }

    /**
     * 重新组织备份文件：表结构在前，数据在后
     */
    private boolean reorganizeBackupFile(Path backupPath) {
        Path tempPath = null;
        try {
            tempPath = Files.createTempFile("backup_reorganize_", ".sql");

            // 分别收集表结构语句和数据语句
            List<String> schemaStatements = new ArrayList<>();
            List<String> dataStatements = new ArrayList<>();

            try (BufferedReader reader = Files.newBufferedReader(backupPath)) {
                String line;
                StringBuilder currentStatement = new StringBuilder();
                boolean inStatement = false;
                boolean isInsertStatement = false;

                while ((line = reader.readLine()) != null) {
                    String trimmedLine = line.trim();

                    // 检测语句开始
                    if (trimmedLine.startsWith("--") || trimmedLine.isEmpty()) {
                        // 注释或空行，直接添加到当前集合
                        if (inStatement && currentStatement.length() > 0) {
                            currentStatement.append(line).append("\n");
                        } else {
                            // 独立的注释行，根据上下文决定归属
                            if (!dataStatements.isEmpty()) {
                                dataStatements.add(line);
                            } else {
                                schemaStatements.add(line);
                            }
                        }
                    } else if (trimmedLine.startsWith("CREATE TABLE") ||
                            trimmedLine.startsWith("DROP TABLE") ||
                            trimmedLine.startsWith("LOCK TABLES") ||
                            trimmedLine.startsWith("UNLOCK TABLES") ||
                            trimmedLine.startsWith("/*!") ) {
                        // 表结构相关语句
                        if (inStatement && currentStatement.length() > 0) {
                            // 结束前一个语句
                            if (isInsertStatement) {
                                dataStatements.add(currentStatement.toString());
                            } else {
                                schemaStatements.add(currentStatement.toString());
                            }
                            currentStatement = new StringBuilder();
                        }
                        currentStatement.append(line).append("\n");
                        inStatement = true;
                        isInsertStatement = false;

                        // 检查是否在同一行结束
                        if (trimmedLine.endsWith(";")) {
                            schemaStatements.add(currentStatement.toString());
                            currentStatement = new StringBuilder();
                            inStatement = false;
                        }
                    } else if (trimmedLine.startsWith("INSERT INTO")) {
                        // 数据插入语句
                        if (inStatement && currentStatement.length() > 0) {
                            // 结束前一个语句
                            schemaStatements.add(currentStatement.toString());
                            currentStatement = new StringBuilder();
                        }
                        currentStatement.append(line).append("\n");
                        inStatement = true;
                        isInsertStatement = true;

                        // 检查是否在同一行结束
                        if (trimmedLine.endsWith(";")) {
                            dataStatements.add(currentStatement.toString());
                            currentStatement = new StringBuilder();
                            inStatement = false;
                        }
                    } else if (inStatement) {
                        // 当前语句的继续
                        currentStatement.append(line).append("\n");

                        // 检查语句是否结束
                        if (trimmedLine.endsWith(";")) {
                            if (isInsertStatement) {
                                dataStatements.add(currentStatement.toString());
                            } else {
                                schemaStatements.add(currentStatement.toString());
                            }
                            currentStatement = new StringBuilder();
                            inStatement = false;
                        }
                    } else {
                        // 其他语句，默认归为表结构
                        schemaStatements.add(line);
                    }
                }

                // 处理最后未完成的语句
                if (inStatement && currentStatement.length() > 0) {
                    if (isInsertStatement) {
                        dataStatements.add(currentStatement.toString());
                    } else {
                        schemaStatements.add(currentStatement.toString());
                    }
                }
            }

            // 统计备份文件中的表数量
            int backupTableCount = countTablesInSchema(schemaStatements);
            System.out.println("📋 备份文件中表数量: " + backupTableCount + " 张表");

            // 写入重新组织的文件：表结构在前，数据在后
            try (BufferedWriter writer = Files.newBufferedWriter(tempPath)) {
                // 写入表头信息
                writer.write("-- Database Backup");
                writer.newLine();
                writer.write("-- Generated: " + new Date());
                writer.newLine();
                writer.write("-- Database: " + getDatabaseName());
                writer.newLine();
                writer.write("-- Table Count: " + backupTableCount);
                writer.newLine();
                writer.write("-- ------------------------------------------------");
                writer.newLine();
                writer.newLine();

                // 写入表结构部分
                for (String schema : schemaStatements) {
                    writer.write(schema);
                    if (!schema.endsWith("\n")) {
                        writer.newLine();
                    }
                }

                // 添加分隔注释
                writer.newLine();
                writer.write("-- ------------------------------------------------");
                writer.newLine();
                writer.write("-- DATA DUMP");
                writer.newLine();
                writer.write("-- ------------------------------------------------");
                writer.newLine();
                writer.newLine();

                // 写入数据部分
                for (String data : dataStatements) {
                    writer.write(data);
                    if (!data.endsWith("\n")) {
                        writer.newLine();
                    }
                }
            }

            System.out.println("📋 文件重组完成: " + schemaStatements.size() + " 个表结构语句, " +
                    dataStatements.size() + " 个数据语句");

            // 用重组后的文件替换原文件
            Files.move(tempPath, backupPath, StandardCopyOption.REPLACE_EXISTING);
            return true;

        } catch (Exception e) {
            System.err.println("重组备份文件失败: " + e.getMessage());
            e.printStackTrace();
            return false;
        } finally {
            if (tempPath != null) {
                try {
                    Files.deleteIfExists(tempPath);
                } catch (IOException e) {
                    System.err.println("清理临时文件失败: " + e.getMessage());
                }
            }
        }
    }

    /**
     * 统计表结构语句中的表数量
     */
    private int countTablesInSchema(List<String> schemaStatements) {
        int tableCount = 0;
        Set<String> tableNames = new HashSet<>();

        for (String statement : schemaStatements) {
            if (statement.trim().startsWith("CREATE TABLE")) {
                // 提取表名：CREATE TABLE `table_name` ...
                String[] parts = statement.trim().split("`");
                if (parts.length >= 2) {
                    tableNames.add(parts[1]);
                }
                tableCount++;
            }
        }

        System.out.println("📊 备份文件中唯一表名数量: " + tableNames.size());
        return tableCount;
    }

    /**
     * 验证备份文件完整性
     */
    private boolean validateBackupFile(Path backupPath, int expectedTableCount) {
        try {
            if (!Files.exists(backupPath) || Files.size(backupPath) == 0) {
                System.err.println("❌ 备份文件不存在或为空");
                return false;
            }

            int backupTableCount = 0;
            boolean hasCreateTable = false;
            boolean hasInsert = false;
            boolean inDataSection = false;
            Set<String> tableNames = new HashSet<>();

            try (BufferedReader reader = Files.newBufferedReader(backupPath)) {
                String line;
                while ((line = reader.readLine()) != null) {
                    String trimmedLine = line.trim();

                    if (trimmedLine.contains("DATA DUMP")) {
                        inDataSection = true;
                    }

                    if (trimmedLine.startsWith("CREATE TABLE")) {
                        hasCreateTable = true;
                        backupTableCount++;

                        // 提取表名
                        String[] parts = trimmedLine.split("`");
                        if (parts.length >= 2) {
                            tableNames.add(parts[1]);
                        }

                        // 在数据部分之后不应该有CREATE TABLE
                        if (inDataSection) {
                            System.err.println("❌ 错误: 在数据部分发现表结构语句");
                            return false;
                        }
                    }

                    if (trimmedLine.startsWith("INSERT INTO")) {
                        hasInsert = true;
                        // 在数据部分之前不应该有INSERT
                        if (!inDataSection) {
                            System.err.println("❌ 错误: 在表结构部分发现数据语句");
                            return false;
                        }
                    }
                }
            }

            System.out.println("✅ 文件结构验证: 表结构=" + hasCreateTable + ", 数据=" + hasInsert);
            System.out.println("📊 表数量统计: 备份文件=" + backupTableCount + " 张表, 唯一表=" + tableNames.size());

            // 表数量对比
            if (expectedTableCount > 0) {
                System.out.println("📊 表数量对比: 数据库=" + expectedTableCount + ", 备份文件=" + backupTableCount);
                if (backupTableCount != expectedTableCount) {
                    System.err.println("❌ 表数量不匹配! 数据库: " + expectedTableCount + ", 备份文件: " + backupTableCount);
                    System.err.println("❌ 可能缺少 " + (expectedTableCount - backupTableCount) + " 张表");
                    return false;
                } else {
                    System.out.println("✅ 表数量匹配");
                }
            }

            if (backupTableCount == 0) {
                System.err.println("❌ 备份文件中没有找到表结构");
                return false;
            }

            if (!hasInsert) {
                System.err.println("❌ 备份文件中没有找到数据插入语句");
                return false;
            }

            return true;

        } catch (Exception e) {
            System.err.println("验证备份文件失败: " + e.getMessage());
            return false;
        }
    }

    /**
     * 强化的mysqldump执行方法
     */
    private boolean executeMysqlDump(Path backupPath) {
        Path configFile = null;
        try {
            String dbName = getDatabaseName();
            String username = config.getDatabase().getUsername();
            String password = config.getDatabase().getPassword();

            // 创建临时配置文件
            configFile = Files.createTempFile("mysql_backup_", ".cnf");
            String configContent = String.format(
                    "[client]\nuser = %s\npassword = %s\nhost = localhost\nmax_allowed_packet=1G",
                    username, password
            );
            Files.write(configFile, configContent.getBytes());

            // 关键：使用分表备份避免截断
            Process process = getProcess(backupPath, configFile, dbName);

            // 读取输出
            StringBuilder output = new StringBuilder();
            try (BufferedReader reader = new BufferedReader(
                    new InputStreamReader(process.getInputStream()))) {
                String line;
                while ((line = reader.readLine()) != null) {
                    output.append(line).append("\n");
                    if (line.contains("Error") && !line.contains("Warning")) {
                        System.err.println("备份错误: " + line);
                    }
                }
            }

            int exitCode = process.waitFor();

            if (exitCode != 0) {
                System.err.println("mysqldump退出码: " + exitCode);
                if (output.length() > 0) {
                    System.err.println("错误输出: " + output.toString());
                }
            }

            return exitCode == 0;

        } catch (Exception e) {
            System.err.println("执行备份命令失败: " + e.getMessage());
            return false;
        } finally {
            if (configFile != null) {
                try {
                    Files.deleteIfExists(configFile);
                } catch (IOException e) {
                    System.err.println("删除临时配置文件失败: " + e.getMessage());
                }
            }
        }
    }

    private Process getProcess(Path backupPath, Path configFile, String dbName) throws IOException {
        ProcessBuilder processBuilder = new ProcessBuilder(
                config.getConfig().getMysqlDumpPath(),
                "--defaults-file=" + configFile,
                "--single-transaction",
                "--skip-comments",
                "--skip-tz-utc",
                "--default-character-set=utf8mb4",  // 修复：使用正确的参数名
                // 禁用扩展INSERT，每行一条记录
                "--skip-extended-insert",
                "--complete-insert",
                // 增加缓冲区大小（调整为合理值）
                "--net-buffer-length=16M",  // 修复：使用允许的最大值
                // 确保备份所有表
                "--all-tablespaces",
                dbName
        );

        processBuilder.redirectOutput(backupPath.toFile());
        processBuilder.redirectErrorStream(true);

        return processBuilder.start();
    }

    /**
     * 清理旧备份
     */
    private void cleanupOldBackups() {
        try {
            long cutoffTime = System.currentTimeMillis() -
                    TimeUnit.DAYS.toMillis(config.getConfig().getRetentionDays());

            int deletedCount = 0;
            Path backupDir = Paths.get(config.getConfig().getBackupDirectory());

            if (!Files.exists(backupDir)) {
                return;
            }

            try (DirectoryStream<Path> stream = Files.newDirectoryStream(
                    backupDir, "backup_*.sql")) {

                for (Path path : stream) {
                    if (Files.isRegularFile(path) &&
                            Files.getLastModifiedTime(path).toMillis() < cutoffTime) {
                        Files.delete(path);
                        deletedCount++;
                        System.out.println("删除过期备份: " + path.getFileName());
                    }
                }
            }

            if (deletedCount > 0) {
                System.out.println("共清理 " + deletedCount + " 个过期备份文件");
            }

        } catch (IOException e) {
            System.err.println("清理旧备份失败: " + e.getMessage());
        }
    }

    /**
     * 创建备份目录
     */
    private void createBackupDirectory() throws IOException {
        Path backupDir = Paths.get(config.getConfig().getBackupDirectory());
        if (!Files.exists(backupDir)) {
            Files.createDirectories(backupDir);
            System.out.println("创建备份目录: " + backupDir.toAbsolutePath());
        }

        // 验证目录可写
        if (!Files.isWritable(backupDir)) {
            throw new IOException("备份目录不可写: " + backupDir.toAbsolutePath());
        }
    }

    /**
     * 获取数据库名称
     */
    private String getDatabaseName() {
        return config.getDatabaseName();
    }
}